<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="小龙的个人博客">
    <meta name="description" content="大数据架构师指南">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>小龙的个人博客</title>
    <link rel="icon" type="image/png" href="/favicon.png">

    <link rel="stylesheet" type="text/css" href="/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/css/my.css">

    <script src="/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 5.0.0"><link rel="alternate" href="/atom.xml" title="小龙的个人博客" type="application/atom+xml">
<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>


<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/" class="waves-effect waves-light">
                    
                    <img src="/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">小龙的个人博客</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友情链接</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">小龙的个人博客</div>
        <div class="logo-desc">
            
            大数据架构师指南
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友情链接
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://gitee.com/carlosgxl" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://gitee.com/carlosgxl" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/medias/featureimages/0.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title"></h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
    }

    .toc-widget .toc-title {
        margin: 35px 0 15px 0;
        padding-left: 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        height: calc(100vh - 250px);
        overflow: auto;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                          <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                          </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2020-08-10
                </div>
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">
        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="分布式解决方案"><a href="#分布式解决方案" class="headerlink" title="分布式解决方案"></a>分布式解决方案</h2><p>nacos 配置中心、注册中心<br>ribbon 负载均衡<br>openfeign rpc调用<br>sentinel 流量控制、熔断<br>gateway api网关<br>sleuth 调用链监控<br>seata 分布式事务</p>
<h2 id=""><a href="#" class="headerlink" title=""></a></h2><h2 id="-1"><a href="#-1" class="headerlink" title=""></a></h2><h2 id="-2"><a href="#-2" class="headerlink" title=""></a></h2><h3 id="1、事务自动配置"><a href="#1、事务自动配置" class="headerlink" title="1、事务自动配置"></a>1、事务自动配置</h3><h3 id="2、事务的坑"><a href="#2、事务的坑" class="headerlink" title="2、事务的坑"></a>2、事务的坑</h3><h1 id="一、本地事务"><a href="#一、本地事务" class="headerlink" title="一、本地事务"></a>一、本地事务</h1><h2 id="1、本地事务的基本性质"><a href="#1、本地事务的基本性质" class="headerlink" title="1、本地事务的基本性质"></a>1、本地事务的基本性质</h2><p>商品新增功能非常复杂，商品管理微服务在service层中调用保存spu和sku相关的方法，为了保证数据的一致性，必然会使用事务。</p>
<p>在JavaEE企业级开发的应用领域，为了保证数据的完整性和一致性，必须引入数据库事务的概念，所以事务管理是企业级应用程序开发中必不可少的技术。</p>
<p>咱们之前玩的事务都是本地事务。所谓本地事务，是指该事务仅在当前工程内有效。</p>
<p><strong>基本性质</strong></p>
<p>事务的概念：事务是逻辑上一组操作，组成这组操作各个逻辑单元，要么一起成功，要么一起失败。</p>
<p>事务的四个特性（ACID）：</p>
<ol>
<li>原子性(atomicity)：“原子”的本意是“<strong>不可再分</strong>”，事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行，要么都不执行。</li>
<li>一致性(consistency)：“一致”指的是数据的一致，具体是指：所有数据都处于满足业务规则的一致性状态。一致性原则要求：一个事务中不管涉及到多少个操作，都必须保证<strong>事务执行之前</strong>数据是正确的，<strong>事务执行之后</strong>数据仍然是正确的。如果一个事务在执行的过程中，其中某一个或某几个操作失败了，则必须将其他所有操作撤销，将数据恢复到事务执行之前的状态，这就是回滚。</li>
<li>隔离性(isolation)：在应用程序实际运行过程中，事务往往是并发执行的，所以很有可能有许多事务同时处理相同的数据，因此每个事务都应该与其他事务隔离开来，防止数据损坏。隔离性原则要求多个事务在<strong>并发执行过程中不会互相干扰</strong>。</li>
<li>持久性(durability)：持久性原则要求事务执行完成后，对数据的修改永久的保存下来，不会因各种系统错误或其他意外情况而受到影响。通常情况下，事务对数据的修改应该被写入到<strong>持久化存储器</strong>中。</li>
</ol>
<h2 id="2、事务的隔离级别"><a href="#2、事务的隔离级别" class="headerlink" title="2、事务的隔离级别"></a>2、事务的隔离级别</h2><p>事务并发引起一些读的问题：</p>
<ul>
<li>脏读 一个事务可以读取另一个事务未提交的数据</li>
<li>不可重复读 一个事务可以读取另一个事务已提交的数据 单条记录前后不匹配</li>
<li>虚读（幻读） 一个事务可以读取另一个事务已提交的数据 读取的数据前后多了点或者少了点</li>
</ul>
<p>并发写：使用mysql默认的锁机制（独占锁）</p>
<p>解决读问题：设置事务隔离级别</p>
<ul>
<li>read uncommitted(0)</li>
<li><strong>read committed(2)</strong></li>
<li><strong>repeatable read(4)</strong></li>
<li>Serializable(8)</li>
</ul>
<p>隔离级别越高，性能越低。</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091406407.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>一般情况下：脏读是不可允许的，不可重复读和幻读是可以被适当允许的。</p>
<p><strong>相关命令</strong></p>
<p>查看全局事务隔离级别：SELECT @@global.tx_isolation</p>
<p>设置全局事务隔离级别：set global transaction isolation level read committed;</p>
<p><strong>查看当前会话事务隔离级别：SELECT @@tx_isolation</strong></p>
<p><strong>设置当前会话事务隔离级别：set session transaction isolation level read committed;</strong></p>
<p>查看mysql默认自动提交状态：select @@autocommit</p>
<p>设置mysql默认自动提交状态：set autocommit = 0;【不自动提交】</p>
<p><strong>开启一个事务：start transaction;</strong></p>
<p><strong>提交事务：commit</strong></p>
<p><strong>回滚事务： rollback</strong></p>
<p>在事务中创建一个保存点：savepoint tx1</p>
<p>回滚到保存点：rollback to tx1</p>
<h2 id="3、事务的传播行为"><a href="#3、事务的传播行为" class="headerlink" title="3、事务的传播行为"></a>3、事务的传播行为</h2><p>事务的传播行为不是jdbc规范中的定义。传播行为主要针对实际开发中的问题</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091441283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>七种传播行为：</p>
<p>REQUIRED 支持当前事务，如果不存在，就新建一个</p>
<p>SUPPORTS 支持当前事务，如果不存在，就不使用事务</p>
<p>MANDATORY 支持当前事务，如果不存在，抛出异常</p>
<p>REQUIRES_NEW 如果有事务存在，挂起当前事务，创建一个新的事务</p>
<p>NOT_SUPPORTED 以非事务方式运行，如果有事务存在，挂起当前事务</p>
<p>NEVER 以非事务方式运行，如果有事务存在，抛出异常</p>
<p>NESTED 如果当前事务存在，则嵌套事务执行（嵌套式事务）</p>
<ul>
<li>依赖于JDBC3.0提供的SavePoint技术</li>
<li>删除用户 删除订单。在删除订单后，设置savePoint，执行删除用户。删除订单和删除用户在同一事务中，删除用户失败，事务回滚savePoint，由用户控制视图提交还是回滚</li>
</ul>
<p>这七种事务传播机制最常用的就两种：</p>
<p>REQUIRED：一个事务，要么成功，要么失败</p>
<p>REQUIRES_NEW：两个不同事务，彼此之间没有关系。一个事务失败了不影响另一个事务</p>
<h3 id="1-4-1-伪代码练习"><a href="#1-4-1-伪代码练习" class="headerlink" title="1.4.1. 伪代码练习"></a>1.4.1. 伪代码练习</h3><p>传播行为伪代码模拟：有a,b,c,d,e等5个方法，a中调用b,c,d,e方法的传播行为在小括号中标出</p>
<pre class=" language-java"><code class="language-java"><span class="token function">a</span><span class="token punctuation">(</span>required<span class="token punctuation">)</span><span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
    <span class="token function">b</span><span class="token punctuation">(</span>required<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">c</span><span class="token punctuation">(</span>requires_new<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">d</span><span class="token punctuation">(</span>required<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">e</span><span class="token punctuation">(</span>requires_new<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment" spellcheck="true">// a方法的业务</span>
<span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">1234567</span></code></pre>
<p>问题：</p>
<ol>
<li>a方法的业务出现异常，会怎样？a,b,d回滚 c,e不回滚</li>
<li>d方法出现异常，会怎样？a,b,d回滚 c,e不回滚</li>
<li>e方法出现异常，会怎样？a,b,d,e回滚 c不回滚，e方法出异常会上抛影响到上级方法</li>
<li>b方法出现异常，会怎样？a,b回滚 c,d,e未执行</li>
</ol>
<p>加点难度：</p>
<pre><code>a(required)&amp;#123;
    b(required)&amp;#123;
        f(requires_new);
        g(required)
    &amp;#125;
    c(requires_new)&amp;#123;
        h(requires_new)
        i(required)
    &amp;#125;
    d(required);
    e(requires_new);
    // a方法的业务
&amp;#125;
12345678910111213</code></pre>
<p>问题：</p>
<ol>
<li>a方法业务出异常</li>
<li>e方法出异常</li>
<li>d方法出异常</li>
<li>h,i方法分别出异常</li>
<li>i方法出异常</li>
<li>f,g方法分别出异常</li>
</ol>
<h3 id="1-4-2-改造商品新增代码"><a href="#1-4-2-改造商品新增代码" class="headerlink" title="1.4.2. 改造商品新增代码"></a>1.4.2. 改造商品新增代码</h3><p>现在商品保存的方法结构如下：</p>
<pre class=" language-java"><code class="language-java">    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">bigSave</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        <span class="token comment" spellcheck="true">/// 1.保存spu相关</span>
        <span class="token comment" spellcheck="true">// 1.1. 保存spu基本信息 spu_info</span>
        Long spuId <span class="token operator">=</span> <span class="token function">saveSpu</span><span class="token punctuation">(</span>spuVo<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// 1.2. 保存spu的描述信息 spu_info_desc</span>
        <span class="token function">saveSpuDesc</span><span class="token punctuation">(</span>spuVo<span class="token punctuation">,</span> spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// 1.3. 保存spu的规格参数信息</span>
        <span class="token function">saveBaseAttr</span><span class="token punctuation">(</span>spuVo<span class="token punctuation">,</span> spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">/// 2. 保存sku相关信息</span>
        <span class="token function">saveSku</span><span class="token punctuation">(</span>spuVo<span class="token punctuation">,</span> spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 保存sku相关信息及营销信息
     * @param spuInfoVO
     */</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">saveSku</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span> 。。。 <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 保存spu基本属性信息
     * @param spuInfoVO
     */</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">saveBaseAttr</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span> 。。。 <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 保存spu描述信息（图片）
     * @param spuInfoVO
     */</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">saveSpuDesc</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span> 。。。 <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 保存spu基本信息
     * @param spuInfoVO
     */</span>
    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">saveSpu</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>  。。。 <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">123456789101112131415161718192021222324252627282930313233343536373839</span></code></pre>
<p>为了测试事务传播行为，我们在SpuInfoService接口中把saveSkuInfoWithSaleInfo、saveBaseAttrs、saveSpuDesc、saveSpuInfo声明为service接口方法。</p>
<pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">SpuInfoService</span> <span class="token keyword">extends</span> <span class="token class-name">IService</span><span class="token operator">&lt;</span>SpuInfoEntity<span class="token operator">></span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>

    PageVo <span class="token function">queryPage</span><span class="token punctuation">(</span>QueryCondition params<span class="token punctuation">)</span><span class="token punctuation">;</span>

    PageVo <span class="token function">querySpuInfo</span><span class="token punctuation">(</span>QueryCondition condition<span class="token punctuation">,</span> Long catId<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">void</span> <span class="token function">saveSpuInfoVO</span><span class="token punctuation">(</span>SpuInfoVO spuInfoVO<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">void</span> <span class="token function">saveSku</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">void</span> <span class="token function">saveBaseAttr</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">void</span> <span class="token function">saveSpuDesc</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">,</span> Long spuId<span class="token punctuation">)</span><span class="token punctuation">;</span>

    Long <span class="token function">saveSpu</span><span class="token punctuation">(</span>SpuVo spuVo<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">12345678910111213141516</span></code></pre>
<p>再把SpuInfoServiceImpl实现类的对应方法改成public：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091508175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h3 id="1-4-3-测试1：同一service-requires-new"><a href="#1-4-3-测试1：同一service-requires-new" class="headerlink" title="1.4.3. 测试1：同一service + requires_new"></a>1.4.3. 测试1：同一service + requires_new</h3><p>springboot 1.x使用事务需要在引导类上添加@EnableTransactionManagement注解开启事务支持</p>
<p>springboot 2.x可直接使用@Transactional玩事务，传播行为默认是REQUIRED</p>
<p>添加事务：<br><img src="https://img-blog.csdnimg.cn/20200702091522718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>这时，在保存商品的主方法中制造异常：</p>
<p><img src="https://img-blog.csdnimg.cn/2020070209153546.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>由于保存商品描述方法使用的是requires_new，spu应该会回滚，spu_desc应该保存成功。</p>
<p>清空pms_spu_desc表，再添加一个spu保存。</p>
<p>结果pms_spu_desc表中依然没有数据。</p>
<p>但是控制台打印了新增pms_spu_desc表的sql语句：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091548153.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>说明saveSpuDesc方法的事务回滚了，也就是说该方法配置的事务传播机制没有生效。</p>
<p>解决方案：</p>
<ol>
<li>把service方法放到不同的service中</li>
<li>使用动态代理对象调用该方法</li>
</ol>
<h3 id="1-4-4-测试2：不同service-requires-new"><a href="#1-4-4-测试2：不同service-requires-new" class="headerlink" title="1.4.4. 测试2：不同service + requires_new"></a>1.4.4. 测试2：不同service + requires_new</h3><p>把saveSpuDesc方法放到SpuDescService中：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091603472.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>在实现类中实现该方法，可以把之前的实现copy过来：</p>
<p><img src="https://img-blog.csdnimg.cn/2020070209161677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>改造SpuServiceImpl中保存商品的方法，调用SpuDescServiceImpl的saveSpuDesc方法：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091637396.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>再次重启gmall-pms，虽然控制台依然报错，但是数据可以保存成功，说明没有在一个事务中。<br><img src="https://img-blog.csdnimg.cn/20200702091649382.png" alt="在这里插入图片描述"></p>
<p>为什么测试1的事务传播行为没有生效，而测试2的事务传播行为生效了？</p>
<p><strong>spring的事务是声明式事务，而声明式事务的本质是Spring AOP，SpringAOP的本质是动态代理。</strong></p>
<p><strong>事务要生效必须是代理对象在调用。</strong></p>
<p>测试1：通过this调用同一个service中的方法，this是指service实现类对象本身，不是代理对象，就相当于方法中的代码粘到了大方法里面，相当于还是一个方法。</p>
<p>测试2：通过其他service对象（spuDescService）调用，这个service对象本质是动态代理对象</p>
<p>接下来debug，打个断点看看：</p>
<ol>
<li>spuDescService：</li>
</ol>
<p><img src="https://img-blog.csdnimg.cn/20200702091702512.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<ol>
<li>this:</li>
</ol>
<p><img src="https://img-blog.csdnimg.cn/20200702091713766.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h3 id="1-4-5-在同一个service中使用传播行为"><a href="#1-4-5-在同一个service中使用传播行为" class="headerlink" title="1.4.5. 在同一个service中使用传播行为"></a>1.4.5. 在同一个service中使用传播行为</h3><p>只需要把测试1中的<code>this.方法名()</code>替换成<code>this代理对象.方法名()</code>即可。</p>
<p>问题是怎么在service中获取当前类的代理对象？</p>
<p>在类中获取代理对象分三个步骤：</p>
<ol>
<li>导入aop的场景依赖：spring-boot-starter-aop</li>
<li>开启AspectJ的自动代理，同时要暴露代理对象：@EnableAspectJAutoProxy(exposeProxy=true)</li>
<li>获取代理对象：SpuInfoService proxy = (SpuInfoService) AopContext.currentProxy();</li>
</ol>
<p>具体如下：</p>
<pre class=" language-xml"><code class="language-xml">        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-aop<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>
1234</code></pre>
<p><img src="https://img-blog.csdnimg.cn/20200702091727278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200702091737706.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>重启后测试：先清空pms_spu_info_desc表中数据</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091748632.png" alt="在这里插入图片描述"></p>
<p>表中数据新增成功，说明saveSpuDesc方法走的是自己的事务，传播行为生效了。</p>
<p>debug可以看到，spuInfoService是一个代理对象。</p>
<p><img src="https://img-blog.csdnimg.cn/2020070209175972.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h2 id="1-5-回滚策略"><a href="#1-5-回滚策略" class="headerlink" title="1.5. 回滚策略"></a>1.5. 回滚策略</h2><p>事务很重要的另一个特征是程序出异常时，会回滚。但并不是所有的异常都会回滚。</p>
<p>默认情况下的回滚策略：</p>
<ul>
<li>运行时异常：不受检异常，没有强制要求try-catch，都会回滚。例如：ArrayOutOfIndex，OutofMemory，NullPointException</li>
<li>编译时异常：受检异常，必须处理，要么try-catch要么throws，都不回滚。例如：FileNotFoundException</li>
</ul>
<p>可以通过@Transactional注解的下面几个属性改变回滚策略：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091810834.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>rollbackFor：指定的异常必须回滚</p>
<p>noRollbackFor：发生指定的异常不用回滚</p>
<h3 id="1-5-1-测试编译时异常不回滚"><a href="#1-5-1-测试编译时异常不回滚" class="headerlink" title="1.5.1. 测试编译时异常不回滚"></a>1.5.1. 测试编译时异常不回滚</h3><p>在商品保存方法中制造一个编译时异常：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091822623.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>重启测试，注意pms_spu表中数据：</p>
<p>控制台报异常：<br><img src="https://img-blog.csdnimg.cn/20200702091832265.png" alt="在这里插入图片描述"></p>
<p>pms_spu表中的数据新增成功了。<br><img src="https://img-blog.csdnimg.cn/20200702091842482.png" alt="在这里插入图片描述"></p>
<p>也就证明了编译时异常不回滚。</p>
<h3 id="1-5-2-定制回滚策略"><a href="#1-5-2-定制回滚策略" class="headerlink" title="1.5.2. 定制回滚策略"></a>1.5.2. 定制回滚策略</h3><p>经过刚才的测试，我们知道：</p>
<ol>
<li>ArithmeticException异常（int i = 1/0）会回滚</li>
<li>FileNotFoundException异常（new FileInputStream(“xxxx”)）不回滚</li>
</ol>
<p>接下来我们来改变一下这个策略：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091901212.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>测试：</p>
<ol>
<li><p>FileNotFoundException：在程序中添加new FileInputStream(“xxxx”)，然后测试。</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091912668.png" alt="在这里插入图片描述"></p>
<p>还是id还是17，说明回滚了（回滚也会占用id=18）</p>
</li>
<li><p>ArithmeticException：在程序中添加int i = 1/0; 然后测试。</p>
</li>
</ol>
<p><img src="https://img-blog.csdnimg.cn/20200702091923422.png" alt="在这里插入图片描述"></p>
<p>id是19，说明没有回滚。</p>
<h2 id="1-6-超时事务"><a href="#1-6-超时事务" class="headerlink" title="1.6. 超时事务"></a>1.6. 超时事务</h2><p>@Transactional注解，还有一个属性是timeout超时时间，单位是秒。<br><img src="https://img-blog.csdnimg.cn/20200702091937102.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>timeout=3：是指第一个sql开始执行到最后一个sql结束执行之间的间隔时间。</p>
<p>即：超时时间（timeout）是指数据库超时，不是业务超时。</p>
<p>改造之前商品保存方法：SpuInfoServiceImpl类中</p>
<p><img src="https://img-blog.csdnimg.cn/20200702091950222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>重启测试：控制台出现事务超时异常</p>
<p><img src="https://img-blog.csdnimg.cn/20200702092000283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h2 id="1-7-只读事务"><a href="#1-7-只读事务" class="headerlink" title="1.7. 只读事务"></a>1.7. 只读事务</h2><p>@Transactional注解最后一个属性是只读事务属性<br><img src="https://img-blog.csdnimg.cn/2020070209201136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>如果一个方法标记为readOnly=true事务，则代表该方法只能查询，不能增删改。readOnly默认为false</p>
<p>给商品新增的事务标记为只读事务：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702092022218.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>测试：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702092034195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h1 id="二、分布式事务"><a href="#二、分布式事务" class="headerlink" title="二、分布式事务"></a>二、分布式事务</h1><h2 id="1、为什么有分布式事务"><a href="#1、为什么有分布式事务" class="headerlink" title="1、为什么有分布式事务"></a>1、为什么有分布式事务</h2><p>​        分布式事务时企业集成中的一个技术难点，也是每一个分布式系统架构中都会涉及到的一个东西，特别是微服务架构中，几乎可以说时无法避免。</p>
<p>​        商品管理微服务在 service 层中除了保存 spu 和 sku 相关的方法，又远程调用 gmall-sms 的保存接口，并在gmall-sms 的 service 层中调用了保存营销信息的相关方法。如果保存营销信息的方法出现异常，会怎样？</p>
<p>​        传统的一个工程内为了保证数据的一致性，使用本地事务。本地事务只能解决同一工程中的事务问题，而现在的场景更加复杂，关系到两个工程模块，怎么保证要么都成功，要么都失败？</p>
<pre><code>     分布式事务就是一次大的操作由不同的小操作组成，这些小的操作分布在不同的服务器上，且属于不同的应用，分布式事务需要保证这些小操作要么全部成功，要么全部失败。

     分布式事务场景：不同应用相同数据库，相同应用不同数据库，不同应用不同数据库。

     ==分布式事务产生的原因：分布式系统异常除了本地事务那些异常之外，还有：机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失…==</code></pre>
<p><img src="https://img-blog.csdnimg.cn/20200702092046327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h2 id="2、CAP-定理与-BASE-理论"><a href="#2、CAP-定理与-BASE-理论" class="headerlink" title="2、CAP 定理与 BASE 理论"></a>2、CAP 定理与 BASE 理论</h2><p>数据库的 ACID 四大特性，已经无法满足我们分布式事务，这个时候又有一些新的大佬提出一些新的理论。</p>
<h3 id="1、CAP-定理"><a href="#1、CAP-定理" class="headerlink" title="1、CAP 定理"></a>1、CAP 定理</h3><p>CAP 原则又称 CAP 定理，指的是在一个分布式系统中</p>
<ul>
<li>一致性（Consistency）<ul>
<li>在分布四系统中的所有数据备份，在同一时刻是否同样的值。（等同于所有节点访问同一份最新的数据副本）</li>
</ul>
</li>
<li>可用性（Availability）<ul>
<li>在集群中一部分节点故障后，集群整体是否还能响应客户端的读写请求。（对数据更新具备高可用性）</li>
</ul>
</li>
<li>分区容错性（Partition tolerance）<ul>
<li>大多数分布式系统都分布在多个子网络，每个自网络就叫一个区（partition）。分区容错的意思时，区间通信可能失败。比如，一台服务器放在中国，另一台服务器放在美国，这就是两个区，他们之间可能无法通信</li>
</ul>
</li>
</ul>
<p>CAP 原则值得是，这三个要素最多只能同时实现两点，不可能三者==兼顾==</p>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200808015133796.png" alt="image-20200808015133796"></p>
<p>一般来说，分区容错无法避免，因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们，剩下的 C 和 A 无法同时做到。</p>
<p>CAP理论就是说在分布式存储系统中，最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题，所以<strong>分区容忍性是我们无法避免的</strong>。所以我们只能在一致性和可用性之间进行权衡，没有系统能同时保证这三点。要么选择CP、要么选择AP。</p>
<p>分布式系统在实现一致性的 raft 算法</p>
<p>问题：zookeeper分布式协调组件 是 <strong>CP</strong> 还是 AP</p>
<p>动画：<a target="_blank" rel="noopener" href="http://thesecretlivesofdata.com/raft/">http://thesecretlivesofdata.com/raft/</a></p>
<p>我们的妥协：BASE</p>
<h3 id="2、面临的问题"><a href="#2、面临的问题" class="headerlink" title="2、面临的问题"></a>2、面临的问题</h3><h3 id="3、BASE-理论"><a href="#3、BASE-理论" class="headerlink" title="3、BASE 理论"></a>3、BASE 理论</h3><p> BASE是对CAP中一致性和可用性权衡的结果，其来源于对大规模互联网系统分布式实践的结论，是基于CAP定理逐步演化而来的，其核心思想是即使无法做到强一致性（Strong consistency），但每个应用都可以根据自身的业务特点，采用适当的方式来使系统达到最终一致性（Eventual consistency）。接下来看看BASE中的三要素：</p>
<ol>
<li><p>Basically Available（<strong>基本可用</strong>）</p>
<p>基本可用是指分布式系统在出现故障的时候，允许损失部分可用性，即保证核心可用。<br>电商大促时，为了应对访问量激增，部分用户可能会被引导到降级页面，服务层也可能只提供降级服务。这就是损失部分可用性的体现。</p>
</li>
<li><p>Soft state（软状态）</p>
<p>软状态是指允许系统存在中间状态，而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本，<strong>允许不同节点间副本同步的延时</strong>就是软状态的体现。mysql replication的异步复制也是一种体现。</p>
</li>
<li><p>Eventually consistent（最终一致性）</p>
<p>最终一致性是指系统中的所有数据副本经过一定时间后，最终能够达到一致的状态。弱一致性和强一致性相反，最终一致性是弱一致性的一种特殊情况。</p>
</li>
</ol>
<p>BASE模型是传统ACID模型的反面，不同于ACID，BASE强调牺牲高一致性，从而获得可用性，数据<strong>允许在一段时间内的不一致，只要保证最终一致就可以了</strong>。</p>
<h3 id="4、强一致性、弱一致性"><a href="#4、强一致性、弱一致性" class="headerlink" title="4、强一致性、弱一致性"></a>4、强一致性、弱一致性</h3><h2 id="3、分布式事务解决方案"><a href="#3、分布式事务解决方案" class="headerlink" title="3、分布式事务解决方案"></a>3、分布式事务解决方案</h2><p>分布式事务是企业集成中的一个<strong>技术难点</strong>，也是每一个分布式系统架构中都会涉及到的一个东西，特别是在微服务架构中，<strong>几乎可以说是无法避免</strong>。</p>
<p>主流的解决方案如下：</p>
<ol>
<li>基于XA协议的两阶段提交（2PC）</li>
<li>TCC编程模式</li>
<li>消息事务+最终一致性</li>
</ol>
<h3 id="1）、两阶段提交（2PC）"><a href="#1）、两阶段提交（2PC）" class="headerlink" title="1）、两阶段提交（2PC）"></a>1）、两阶段提交（2PC）</h3><p>数据库支持的 【2PC2phase commit 二阶提交】，又叫做 XA Transactions。</p>
<p>MySOL 从 5.5 版本开始支持，SQL Server 2005 开始支持，Oracle7 开始支持。</p>
<p>其中，XA 是一个两阶段提交协议，该协议分为以下两个阶段：</p>
<p>第一阶段：事务协调器要求每个涉及到事务的数据库预提交（precommit)此操作，并反映是否可以提交。</p>
<p>第二阶段：事务协调器要求每个数据库提交数据。<br>其中，如果有任何一个数据库否决此次提交，那么所有数据库都会被要求回滚它们在此事务中的那部分信息。</p>
<p><img src="C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200808015806014.png" alt="image-20200808015806014"></p>
<p>2PC即两阶段提交协议，是将整个事务流程分为两个阶段，准备阶段（Prepare phase）、提交阶段（commit<br>phase），2是指两个阶段，P是指准备阶段，C是指提交阶段。</p>
<p><img src="https://img-blog.csdnimg.cn/2020070209210937.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>第一阶段：事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作，并反映是否可以提交.</p>
<p>第二阶段：事务协调器要求每个数据库提交数据。</p>
<p>其中，如果有任何一个数据库否决此次提交，那么所有数据库都会被要求回滚它们在此事务中的那部分信息。</p>
<p>目前主流数据库均支持2PC【2 Phase Commit】</p>
<p>XA 是一个两阶段提交协议，又叫做 XA Transactions。</p>
<p>MySQL从5.5版本开始支持，SQL Server 2005 开始支持，Oracle 7 开始支持。</p>
<p> 总的来说，XA协议比较简单，而且一旦商业数据库实现了XA协议，使用分布式事务的成本也比较低。但是，XA也有致命的缺点，那就是性能不理想，特别是在交易下单链路，往往并发量很高，XA无法满足高并发场景。</p>
<ol>
<li>两阶段提交涉及多次节点间的网络通信，通信时间太长！</li>
<li>事务时间相对于变长了，锁定的资源的时间也变长了，造成资源等待时间也增加好多。</li>
<li>XA目前在商业数据库支持的比较理想，在mysql数据库中支持的不太理想，mysql的XA实现，没有记录prepare阶段日志，主备切换会导致主库与备库数据不一致。许多nosql也没有支持XA，这让XA的应用场景变得非常狭隘。</li>
</ol>
<p>对应的开源框架：atomikos</p>
<h3 id="2-、柔性事务–TCC-补偿式事务"><a href="#2-、柔性事务–TCC-补偿式事务" class="headerlink" title="2)、柔性事务–TCC 补偿式事务"></a>2)、柔性事务–TCC 补偿式事务</h3><p>是一种编程式分布式事务解决方案。</p>
<p>TCC 其实就是采用的补偿机制，其核心思想是：针对每个操作，都要注册一个与其对应的确认和补偿（撤销）操作。TCC模式要求从服务提供三个接口：Try、Confirm、Cancel。</p>
<ul>
<li>Try：主要是对业务系统做检测及资源预留</li>
<li>Confirm：真正执行业务，不作任何业务检查；只使用Try阶段预留的业务资源；Confirm操作满足幂等性。</li>
<li>Cancel：释放Try阶段预留的业务资源；Cancel操作满足幂等性。</li>
</ul>
<p>整个TCC业务分成两个阶段完成：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702092123725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>第一阶段：主业务服务分别调用所有从业务的try操作，并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败，进入第二阶段。</p>
<p>第二阶段：活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功，则活动管理器调用所有从业务活动的confirm操作。否则调用所有从业务服务的cancel操作。</p>
<p>举个例子，假如 Bob 要向 Smith 转账100元，思路大概是：</p>
<p>我们有一个本地方法，里面依次调用</p>
<ol>
<li>首先在 Try 阶段，要先检查Bob的钱是否充足，并把这100元锁住，Smith账户也冻结起来。</li>
<li>在 Confirm 阶段，执行远程调用的转账的操作，转账成功进行解冻。</li>
<li>如果第2步执行成功，那么转账成功，如果第二步执行失败，则调用远程冻结接口对应的解冻方法 (Cancel)。</li>
</ol>
<p>缺点：</p>
<ul>
<li>Canfirm和Cancel的<strong>幂等性</strong>很难保证。</li>
<li>这种方式缺点比较多，通常在<strong>复杂场景下是不推荐使用</strong>的，除非是非常简单的场景，非常容易提供回滚Cancel，而且依赖的服务也非常少的情况。</li>
<li>这种实现方式会造成<strong>代码量庞大，耦合性高</strong>。而且非常有局限性，因为有很多的业务是无法很简单的实现回滚的，如果串行的服务很多，回滚的成本实在太高。</li>
</ul>
<p>不少大公司里，其实都是自己研发 TCC 分布式事务框架的，专门在公司内部使用。国内开源出去的：ByteTCC，TCC-transaction，Himly。</p>
<h3 id="3）、柔性事务-可靠消息-最终一致性方案（异步确保性）"><a href="#3）、柔性事务-可靠消息-最终一致性方案（异步确保性）" class="headerlink" title="3）、柔性事务-可靠消息+最终一致性方案（异步确保性）"></a>3）、柔性事务-可靠消息+最终一致性方案（异步确保性）</h3><p>基于消息中间件的两阶段提交往往用在高并发场景下，将一个分布式事务拆成一个消息事务（A系统的本地操作+发消息）+B系统的本地操作，其中B系统的操作由消息驱动，只要消息事务成功，那么A操作一定成功，消息也一定发出来了，这时候B会收到消息去执行本地操作，如果本地操作失败，消息会重投，直到B操作成功，这样就变相地实现了A与B的分布式事务。</p>
<p><img src="https://img-blog.csdnimg.cn/20200702092457358.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>虽然上面的方案能够完成A和B的操作，但是A和B并不是严格一致的，而是最终一致的，我们在这里牺牲了一致性，换来了性能的大幅度提升。当然，这种玩法也是有风险的，如果B一直执行不成功，那么一致性会被破坏，具体要不要玩，还是得看业务能够承担多少风险。</p>
<p>适用于高并发最终一致</p>
<p>低并发基本一致：二阶段提交</p>
<p>高并发强一致：没有解决方案</p>
<h1 id="三、分布式事务框架-seata"><a href="#三、分布式事务框架-seata" class="headerlink" title="三、分布式事务框架-seata"></a>三、分布式事务框架-seata</h1><p>seata：Simple Extensible Autonomous Transaction Architecture</p>
<p>官方：<a target="_blank" rel="noopener" href="https://github.com/seata/seata">https://github.com/seata/seata</a></p>
<p>中文wiki：<a target="_blank" rel="noopener" href="https://github.com/seata/seata">https://github.com/seata/seata</a></p>
<p>2019 年 1 月，阿里巴巴中间件团队发起了开源项目 Fescar（Fast &amp; EaSy Commit And Rollback），和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样，简单和高效，并逐步解决开发者们遇到的分布式事务方面的所有难题。</p>
<p><strong>Fescar 开源后，蚂蚁金服加入 Fescar 社区参与共建，并在 Fescar 0.4.0 版本中贡献了 TCC 模式。</strong></p>
<p>为了打造更中立、更开放、生态更加丰富的分布式事务开源社区，经过社区核心成员的投票，大家决定对 Fescar 进行品牌升级，并更名为 <strong>Seata</strong>，意为：<strong>Simple Extensible Autonomous Transaction Architecture</strong>，是一套一站式分布式事务解决方案。</p>
<p>Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累，并沉淀了新零售、云计算和新金融等场景下丰富的实践经验，但要实现<strong>适用于所有的分布式事务场景</strong>的愿景，仍有很长的路要走。因此，我们决定建立一个完全中立的分布式事务组织，希望更多的企业、开发者能够加入我们，一起打造 Seata。</p>
<p>历史：</p>
<p><strong>Ant Financial</strong></p>
<p>XTS：Extended Transaction Service，可扩展事务服务。蚂蚁金服中间件团队自2007年以来开发了分布式事务中间件，广泛应用于Ant Financial，解决了跨数据库和服务的数据一致性问题。</p>
<p>DTX：Distributed Transaction Extended。自2013年以来，XTS已在Ant Financial Cloud上发布，名称为DTX。</p>
<p><strong>阿里巴巴</strong></p>
<p>TXC：Taobao Transaction Constructor。阿里巴巴中间件团队自2014年起启动该项目，以解决因应用程序架构从单片机改为微服务而导致的分布式事务问题。</p>
<p>GTS：Global Transaction Service。 TXC作为Aliyun中间件产品，新名称GTS自2016年起发布。</p>
<p>Fescar：我们从2019年开始基于TXC / GTS开源开源项目Fescar，以便在未来与社区密切合作。</p>
<p><strong>Seata社区</strong></p>
<p>Seata：简单的可扩展自治交易架构。 Ant Financial加入Fescar，使其成为一个更加中立和开放的分布式服务社区，并将Fescar更名为Seata。</p>
<h2 id="3-1-结构"><a href="#3-1-结构" class="headerlink" title="3.1. 结构"></a>3.1. 结构</h2><p>Seata有3个基本组件：</p>
<ul>
<li>Transaction Coordinator(TC)：事务协调器，维护全局事务的运行状态，负责协调并驱动全局事务的提交或回滚。</li>
<li>Transaction Manager™：事务管理器，控制<strong>全局事务</strong>的边界，负责开启一个全局事务，并最终发起全局提交或全局回滚的决议。</li>
<li>Resource Manager(RM)：资源管理器，控制<strong>分支事务</strong>，负责分支注册、状态汇报，并接收事务协调器的指令，驱动分支（本地）事务的提交和回滚。</li>
</ul>
<p>全局事务与分支事务：</p>
<p>a Distributed Transaction is a Global Transaction which is made up with a batch of Branch Transaction, and normally Branch Transaction is just Local Transaction.<br><img src="https://img-blog.csdnimg.cn/20200702093058879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>Seata管理分布式事务的典型生命周期：</p>
<ul>
<li>TM 向 TC 申请开启一个全局事务，全局事务创建成功并生成一个全局唯一的 XID。</li>
<li>XID 在微服务调用链路的上下文中传播。</li>
<li>RM 向 TC 注册分支事务，将其纳入 XID 对应全局事务的管辖。</li>
<li>TM 向 TC 发起针对 XID 的全局提交或回滚决议。</li>
<li>TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。</li>
</ul>
<p><img src="https://img-blog.csdnimg.cn/20200702093110637.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>至此，seata的协议机制总体上看与 XA 是一致的。但是是有差别的：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093122383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>XA 方案的 RM 实际上是在数据库层，RM 本质上就是数据库自身（通过提供支持 XA 的驱动程序来供应用使用）。</p>
<p>而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的，不依赖于数据库本身对协议的支持，当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的：应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。</p>
<p>这个设计，剥离了分布式事务方案对数据库在 <strong>协议支持</strong> 上的要求。</p>
<h2 id="3-2-快速入门"><a href="#3-2-快速入门" class="headerlink" title="3.2. 快速入门"></a>3.2. 快速入门</h2><p>springCloud整合seata：<a target="_blank" rel="noopener" href="https://github.com/seata/seata-samples/tree/master/springcloud-jpa-seata">https://github.com/seata/seata-samples/tree/master/springcloud-jpa-seata</a></p>
<p>官方提供了案例工程：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093139873.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>案例工程结构如下：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093154548.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>接下来，把案例工程下载下来，根据管方案例学习seata该怎么使用。</p>
<p>或者使用课前资料中下载好的seata案例：</p>
<p>解压后把springcloud-jpa-seata工程，导入eclipse</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093547690.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>导入后：<br><img src="https://img-blog.csdnimg.cn/20200702093558368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>怎么启动这些工程，官方也给出文档：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093609847.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h3 id="3-2-1-执行sql"><a href="#3-2-1-执行sql" class="headerlink" title="3.2.1. 执行sql"></a>3.2.1. 执行sql</h3><p>把压缩包下springcloud-jpa-seata/sql目录下的sql文件导入数据库</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093619505.png" alt="在这里插入图片描述"></p>
<p>导入后，有案例工程微服务对应的3个数据库：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093629507.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h3 id="3-2-2-下载并启动seata-server"><a href="#3-2-2-下载并启动seata-server" class="headerlink" title="3.2.2. 下载并启动seata-server"></a>3.2.2. 下载并启动seata-server</h3><p>下载地址：<a target="_blank" rel="noopener" href="https://github.com/seata/seata/releases">https://github.com/seata/seata/releases</a></p>
<p>自行下载，或者使用课前资料中提供的。</p>
<p>先修改conf下的配置文件（registry.conf）：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093641781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>修改内容如下：也可以不修改</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093649860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>切换到bin目录下，双击启动即可</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093658191.png" alt="在这里插入图片描述"></p>
<h3 id="3-2-3-启动各个微服务"><a href="#3-2-3-启动各个微服务" class="headerlink" title="3.2.3. 启动各个微服务"></a>3.2.3. 启动各个微服务</h3><p>先修改各个微服务的application.properties中有关数据库连接信息的配置，例如：storage-service</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093707343.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<pre class=" language-properties"><code class="language-properties"><span class="token attr-name">spring.datasource.url</span><span class="token punctuation">=</span><span class="token attr-value">jdbc:mysql://172.16.116.100:3306/db_storage?useSSL=false&amp;serverTimezone=UTC</span>
<span class="token attr-name">spring.datasource.username</span><span class="token punctuation">=</span><span class="token attr-value">root</span>
<span class="token attr-name">spring.datasource.password</span><span class="token punctuation">=</span><span class="token attr-value">root</span>
123</code></pre>
<p>其他微服务，请自行修改。注意：url中的数据库名要对应。</p>
<p>然后，依次启动四个微服务。</p>
<h3 id="3-2-4-测试"><a href="#3-2-4-测试" class="headerlink" title="3.2.4. 测试"></a>3.2.4. 测试</h3><p>依据官方文档，进行测试：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093719317.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>测试前，观察数据库：<br><img src="https://img-blog.csdnimg.cn/20200702093727681.png" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20200702093740265.png" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200702093749765.png" alt="在这里插入图片描述"></p>
<p><strong>正常提交情况：</strong></p>
<p>在浏览器中输入：<a target="_blank" rel="noopener" href="http://127.0.0.1:8084/purchase/commit">http://127.0.0.1:8084/purchase/commit</a><br><img src="https://img-blog.csdnimg.cn/2020070209375957.png" alt="在这里插入图片描述"></p>
<p>测试结果：<br><img src="https://img-blog.csdnimg.cn/2020070209381038.png" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200702093820686.png" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200702093829800.png" alt="在这里插入图片描述"><br>business-service源码，其实就是1001账户花了5金钱购买了2001物品。前后数据一致</p>
<p><strong>异常回滚情况：</strong></p>
<p>在浏览器访问：<a target="_blank" rel="noopener" href="http://127.0.0.1:8084/purchase/rollback">http://127.0.0.1:8084/purchase/rollback</a></p>
<p><img src="https://img-blog.csdnimg.cn/20200702093840635.png" alt="在这里插入图片描述"></p>
<p>查看数据库，会发现各个表数据不变！！</p>
<p>查看控制台，account-service、order-service、business-service报错！这是因为business-service的rollback请求的业务（BusinessController）是这样的：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093850634.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>在account-service的AccountService中判断账号是否是1002，是就抛出运行时异常。<br><img src="https://img-blog.csdnimg.cn/20200702093859783.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>总结流程：浏览器访问/rollback –&gt; business-service的Controller让1002账户买东西 –&gt; order-service创建订单，并远程调用1002的账户信息扣款 –&gt; account-service扣款，并在最后制造一个运行时异常。</p>
<p>导致：account-service异常 –&gt; 调用方order-service异常 –&gt; 调用方business-service异常。</p>
<p>但是：storage-service执行正常。</p>
<p>由于business-service使用了分布式事务，所以storage-service数据要回滚。最后都没有数据更新</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093910472.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>如果，把@GlobalTransactional注解去掉或者改成@Transactional注解会怎么样？</p>
<p>自行测试一下。肯定会出现分布式事务问题：即库存会被正常扣掉</p>
<h2 id="3-3-快速入门案例的启示"><a href="#3-3-快速入门案例的启示" class="headerlink" title="3.3. 快速入门案例的启示"></a>3.3. 快速入门案例的启示</h2><p>启示玩任何技术，无非就是三个东西：</p>
<ol>
<li>依赖</li>
<li>配置</li>
<li>注解</li>
</ol>
<p>接下来以storage-service为例，总结一下seata的玩法。</p>
<h3 id="3-3-1-前提：日志表"><a href="#3-3-1-前提：日志表" class="headerlink" title="3.3.1. 前提：日志表"></a>3.3.1. 前提：日志表</h3><p>每个数据库都要有一张undo_log日志表，sql如下：</p>
<pre class=" language-sql"><code class="language-sql"><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>

<span class="token comment" spellcheck="true">-- ----------------------------</span>
<span class="token comment" spellcheck="true">-- Table structure for undo_log</span>
<span class="token comment" spellcheck="true">-- ----------------------------</span>
<span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>undo_log<span class="token punctuation">`</span><span class="token punctuation">;</span>
<span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>undo_log<span class="token punctuation">`</span> <span class="token punctuation">(</span>
  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>xid<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>context<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>rollback_info<span class="token punctuation">`</span> <span class="token keyword">longblob</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_status<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_created<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>ext<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token keyword">UNIQUE</span> <span class="token keyword">KEY</span> <span class="token punctuation">`</span>ux_undo_log<span class="token punctuation">`</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>xid<span class="token punctuation">`</span><span class="token punctuation">,</span><span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token operator">=</span><span class="token number">2</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8<span class="token punctuation">;</span>
<span class="token number">12345678910111213141516171819</span></code></pre>
<h3 id="3-3-2-先看依赖"><a href="#3-3-2-先看依赖" class="headerlink" title="3.3.2. 先看依赖"></a>3.3.2. 先看依赖</h3><p>引入了两个和seata有关的依赖</p>
<pre class=" language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-alibaba-seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.0.0.RELEASE<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>seata-all<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>0.8.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>
12345678910</code></pre>
<h3 id="3-3-3-再看配置"><a href="#3-3-3-再看配置" class="headerlink" title="3.3.3. 再看配置"></a>3.3.3. 再看配置</h3><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kpPDyFQw-1593652420890)(assets/1567754876355.png)]</p>
<p>application.properties：就是一些常规配置，可略过</p>
<p>还剩下3个配置文件：</p>
<ol>
<li>registry.conf：配置注册中心和配置中心，默认是file。</li>
<li>file.conf：seata工作规则信息</li>
<li>DataSourceConfig：配置代理数据源实现分支事务，如果没有注入，事务无法成功回滚</li>
</ol>
<h4 id="3-3-3-1-registry-conf"><a href="#3-3-3-1-registry-conf" class="headerlink" title="3.3.3.1. registry.conf"></a>3.3.3.1. registry.conf</h4><p>该文件包含两部分配置：</p>
<ol>
<li>注册中心</li>
<li>配置中心</li>
</ol>
<p>注册中心：</p>
<pre><code>registry &amp;#123; # 注册中心配置
  # 可选项：file 、nacos 、eureka、redis、zk
  type = "nacos" # 指定nacos注册中心，默认是file。由于项目整体使用nacos，所以后续选择nacos

  nacos &amp;#123;
    serverAddr = "127.0.0.1:8848"
    namespace = "public"
    cluster = "default"
  &amp;#125;
  eureka &amp;#123;
    serviceUrl = "http://localhost:1001/eureka"
    application = "default"
    weight = "1"
  &amp;#125;
  redis &amp;#123;
    serverAddr = "localhost:6381"
    db = "0"
  &amp;#125;
  zk &amp;#123;
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  &amp;#125;
  file &amp;#123;
    name = "file.conf"
  &amp;#125;
&amp;#125;
12345678910111213141516171819202122232425262728</code></pre>
<p>配置中心</p>
<pre><code>config &amp;#123; # 配置中心
  # 可选项：file、nacos 、apollo、zk
  type = "file" # 指向file配置中心，也可以指向nacos等其他注册中心

  nacos &amp;#123;
    serverAddr = "localhost"
    namespace = "public"
    cluster = "default"
  &amp;#125;
  apollo &amp;#123;
    app.id = "fescar-server"
    apollo.meta = "http://192.168.1.204:8801"
  &amp;#125;
  zk &amp;#123;
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  &amp;#125;
  file &amp;#123;
    name = "file.conf"   # 通过file.conf配置seata参数，指向第二个配置文件
  &amp;#125;
&amp;#125;
12345678910111213141516171819202122</code></pre>
<h4 id="3-3-3-2-file-conf"><a href="#3-3-3-2-file-conf" class="headerlink" title="3.3.3.2. file.conf"></a>3.3.3.2. file.conf</h4><p>该文件的命名取决于registry.conf配置中心的配置</p>
<p>由于registry.conf中配置的是</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093926624.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>也就是说：file.conf文件名取决于registry的配置中心配置，如果registry配置的配置中心不是file，可以没有改文件。例如：如果配置中心是nacos，这是file.conf文件就不需要了，把file.conf文件内容交给nacos就可</p>
<p>网络传输配置：</p>
<pre class=" language-properties"><code class="language-properties"><span class="token attr-name">transport</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">  # tcp udt unix-domain-socket</span>
<span class="token attr-name">  type</span> <span class="token punctuation">=</span> <span class="token attr-value">"TCP"</span>
<span class="token comment" spellcheck="true">  #NIO NATIVE</span>
<span class="token attr-name">  server</span> <span class="token punctuation">=</span> <span class="token attr-value">"NIO"</span>
<span class="token comment" spellcheck="true">  #enable heartbeat</span>
<span class="token attr-name">  heartbeat</span> <span class="token punctuation">=</span> <span class="token attr-value">true</span>
<span class="token comment" spellcheck="true">  #thread factory for netty</span>
<span class="token attr-name">  thread-factory</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">    boss-thread-prefix</span> <span class="token punctuation">=</span> <span class="token attr-value">"NettyBoss"</span>
<span class="token attr-name">    worker-thread-prefix</span> <span class="token punctuation">=</span> <span class="token attr-value">"NettyServerNIOWorker"</span>
<span class="token attr-name">    server-executor-thread-prefix</span> <span class="token punctuation">=</span> <span class="token attr-value">"NettyServerBizHandler"</span>
<span class="token attr-name">    share-boss-worker</span> <span class="token punctuation">=</span> <span class="token attr-value">false</span>
<span class="token attr-name">    client-selector-thread-prefix</span> <span class="token punctuation">=</span> <span class="token attr-value">"NettyClientSelector"</span>
<span class="token attr-name">    client-selector-thread-size</span> <span class="token punctuation">=</span> <span class="token attr-value">1</span>
<span class="token attr-name">    client-worker-thread-prefix</span> <span class="token punctuation">=</span> <span class="token attr-value">"NettyClientWorkerThread"</span>
<span class="token comment" spellcheck="true">    # netty boss thread size,will not be used for UDT</span>
<span class="token attr-name">    boss-thread-size</span> <span class="token punctuation">=</span> <span class="token attr-value">1</span>
<span class="token comment" spellcheck="true">    #auto default pin or 8</span>
<span class="token attr-name">    worker-thread-size</span> <span class="token punctuation">=</span> <span class="token attr-value">8</span>
  &amp;#125;
&amp;#125;
12345678910111213141516171819202122</code></pre>
<p>事务日志存储配置：该部分配置仅在seata-server中使用，如果选择db请配合seata.sql使用</p>
<pre class=" language-properties"><code class="language-properties"><span class="token comment" spellcheck="true">## transaction log store, only used in seata-server</span>
<span class="token attr-name">store</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">  ## store mode: file、db</span>
<span class="token attr-name">  mode</span> <span class="token punctuation">=</span> <span class="token attr-value">"file"</span>

<span class="token comment" spellcheck="true">  ## file store property</span>
<span class="token attr-name">  file</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">    ## store location dir</span>
<span class="token attr-name">    dir</span> <span class="token punctuation">=</span> <span class="token attr-value">"sessionStore"</span>
  &amp;#125;

<span class="token comment" spellcheck="true">  ## database store property</span>
<span class="token attr-name">  db</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.</span>
<span class="token attr-name">    datasource</span> <span class="token punctuation">=</span> <span class="token attr-value">"dbcp"</span>
<span class="token comment" spellcheck="true">    ## mysql/oracle/h2/oceanbase etc.</span>
<span class="token attr-name">    db-type</span> <span class="token punctuation">=</span> <span class="token attr-value">"mysql"</span>
<span class="token attr-name">    driver-class-name</span> <span class="token punctuation">=</span> <span class="token attr-value">"com.mysql.jdbc.Driver"</span>
<span class="token attr-name">    url</span> <span class="token punctuation">=</span> <span class="token attr-value">"jdbc:mysql://127.0.0.1:3306/seata"</span>
<span class="token attr-name">    user</span> <span class="token punctuation">=</span> <span class="token attr-value">"mysql"</span>
<span class="token attr-name">    password</span> <span class="token punctuation">=</span> <span class="token attr-value">"mysql"</span>
  &amp;#125;
&amp;#125;
1234567891011121314151617181920212223</code></pre>
<p>*当前微服务在seata服务器中注册的信息配置：</p>
<pre class=" language-properties"><code class="language-properties"><span class="token attr-name">service</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">  # 事务分组，默认：$&amp;#123;spring.applicaiton.name&amp;#125;-fescar-service-group，可以随便写</span>
<span class="token attr-name">  vgroup_mapping.$&amp;#123;spring.application.name&amp;#125;-fescar-service-group</span> <span class="token punctuation">=</span> <span class="token attr-value">"default"</span>
<span class="token comment" spellcheck="true">  # 仅支持单节点，不要配置多地址，这里的default要和事务分组的值一致</span>
<span class="token attr-name">  default.grouplist</span> <span class="token punctuation">=</span> <span class="token attr-value">"127.0.0.1:8091" #seata-server服务器地址，默认是8091</span>
<span class="token comment" spellcheck="true">  # 降级，当前不支持</span>
<span class="token attr-name">  enableDegrade</span> <span class="token punctuation">=</span> <span class="token attr-value">false</span>
<span class="token comment" spellcheck="true">  # 禁用全局事务</span>
<span class="token attr-name">  disableGlobalTransaction</span> <span class="token punctuation">=</span> <span class="token attr-value">false</span>
&amp;#125;
12345678910</code></pre>
<p>客户端相关工作的机制</p>
<pre class=" language-properties"><code class="language-properties"><span class="token attr-name">client</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">  rm</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">    async.commit.buffer.limit</span> <span class="token punctuation">=</span> <span class="token attr-value">10000</span>
<span class="token attr-name">    lock</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">      retry.internal</span> <span class="token punctuation">=</span> <span class="token attr-value">10</span>
<span class="token attr-name">      retry.times</span> <span class="token punctuation">=</span> <span class="token attr-value">30</span>
<span class="token attr-name">      retry.policy.branch-rollback-on-conflict</span> <span class="token punctuation">=</span> <span class="token attr-value">true</span>
    &amp;#125;
<span class="token attr-name">    report.retry.count</span> <span class="token punctuation">=</span> <span class="token attr-value">5</span>
<span class="token attr-name">    table.meta.check.enable</span> <span class="token punctuation">=</span> <span class="token attr-value">false</span>
<span class="token attr-name">    report.success.enable</span> <span class="token punctuation">=</span> <span class="token attr-value">true</span>
  &amp;#125;
<span class="token attr-name">  tm</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">    commit.retry.count</span> <span class="token punctuation">=</span> <span class="token attr-value">5</span>
<span class="token attr-name">    rollback.retry.count</span> <span class="token punctuation">=</span> <span class="token attr-value">5</span>
  &amp;#125;
<span class="token attr-name">  undo</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">    data.validation</span> <span class="token punctuation">=</span> <span class="token attr-value">true</span>
<span class="token attr-name">    log.serialization</span> <span class="token punctuation">=</span> <span class="token attr-value">"jackson"</span>
<span class="token attr-name">    log.table</span> <span class="token punctuation">=</span> <span class="token attr-value">"undo_log"</span>
  &amp;#125;
<span class="token attr-name">  log</span> <span class="token attr-value">&amp;#123;</span>
<span class="token attr-name">    exceptionRate</span> <span class="token punctuation">=</span> <span class="token attr-value">100</span>
  &amp;#125;
<span class="token attr-name">  support</span> <span class="token attr-value">&amp;#123;</span>
<span class="token comment" spellcheck="true">    # auto proxy the DataSource bean</span>
<span class="token attr-name">    spring.datasource.autoproxy</span> <span class="token punctuation">=</span> <span class="token attr-value">false</span>
  &amp;#125;
&amp;#125;
1234567891011121314151617181920212223242526272829</code></pre>
<h4 id="3-3-3-3-DataSourceConfig"><a href="#3-3-3-3-DataSourceConfig" class="headerlink" title="3.3.3.3. DataSourceConfig"></a>3.3.3.3. DataSourceConfig</h4><p>每一个微服务原来自己的数据源都必须使用DataSourceProxy代理，这样seata才能掌控所有事务。</p>
<pre class=" language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DataSourceConfig</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>

    <span class="token annotation punctuation">@Bean</span>
    <span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"spring.datasource"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> DruidDataSource <span class="token function">druidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DruidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 需要将 DataSourceProxy 设置为主数据源，否则事务无法回滚
     *
     * @param druidDataSource The DruidDataSource
     * @return The default datasource
     */</span>
    <span class="token annotation punctuation">@Primary</span>
    <span class="token annotation punctuation">@Bean</span><span class="token punctuation">(</span><span class="token string">"dataSource"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> DataSource <span class="token function">dataSource</span><span class="token punctuation">(</span>DruidDataSource druidDataSource<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DataSourceProxy</span><span class="token punctuation">(</span>druidDataSource<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">123456789101112131415161718192021</span></code></pre>
<h3 id="3-3-4-最后注解"><a href="#3-3-4-最后注解" class="headerlink" title="3.3.4. 最后注解"></a>3.3.4. 最后注解</h3><p>主业务方法添加全局事务：@GlobalTransactional</p>
<p>分支业务方法添加本地事务注解：@Transactional</p>
<h2 id="3-4-给商品新增添加分布式事务"><a href="#3-4-给商品新增添加分布式事务" class="headerlink" title="3.4. 给商品新增添加分布式事务"></a>3.4. 给商品新增添加分布式事务</h2><h3 id="3-4-1-添加undo-log日志表"><a href="#3-4-1-添加undo-log日志表" class="headerlink" title="3.4.1. 添加undo_log日志表"></a>3.4.1. 添加undo_log日志表</h3><p>给每个数据库添加undo_log日志表：</p>
<pre class=" language-sql"><code class="language-sql"><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>

<span class="token comment" spellcheck="true">-- ----------------------------</span>
<span class="token comment" spellcheck="true">-- Table structure for undo_log</span>
<span class="token comment" spellcheck="true">-- ----------------------------</span>
<span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>undo_log<span class="token punctuation">`</span><span class="token punctuation">;</span>
<span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>undo_log<span class="token punctuation">`</span> <span class="token punctuation">(</span>
  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>xid<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>context<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>rollback_info<span class="token punctuation">`</span> <span class="token keyword">longblob</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_status<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_created<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>log_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token punctuation">`</span>ext<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token keyword">UNIQUE</span> <span class="token keyword">KEY</span> <span class="token punctuation">`</span>ux_undo_log<span class="token punctuation">`</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>xid<span class="token punctuation">`</span><span class="token punctuation">,</span><span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token operator">=</span><span class="token number">2</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8<span class="token punctuation">;</span>
<span class="token number">12345678910111213141516171819</span></code></pre>
<p>例如：guli_pms</p>
<p><img src="https://img-blog.csdnimg.cn/2020070209394418.png" alt="在这里插入图片描述"></p>
<h3 id="3-4-2-改造gmall-pms"><a href="#3-4-2-改造gmall-pms" class="headerlink" title="3.4.2. 改造gmall-pms"></a>3.4.2. 改造gmall-pms</h3><p>在pom.xml中同一引入seata的依赖：</p>
<pre class=" language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-alibaba-seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.0.0.RELEASE<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>seata-all<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>0.8.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>
12345678910</code></pre>
<p>添加配置文件：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702093954459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>registry.conf内容如下：</p>
<pre><code>registry &amp;#123;
  type = "nacos"
  nacos &amp;#123;
    serverAddr = "localhost:8848"
    namespace = "public"
    cluster = "default"
  &amp;#125;
&amp;#125;

config &amp;#123;
  type = "file"
  file &amp;#123;
    name = "file.conf"
  &amp;#125;
&amp;#125;
123456789101112131415</code></pre>
<p>file.conf内容如下：</p>
<pre><code>service &amp;#123;
  #vgroup-&gt;rgroup
  # 根据工程的服务名修改
  vgroup_mapping.pms-service-fescar-service-group = "default"
  #only support single node
  default.grouplist = "127.0.0.1:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
&amp;#125;
1234567891011</code></pre>
<p>DataSourceConfig配置：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702094005136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>内容如下：</p>
<pre class=" language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DataSourceConfig</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>

    <span class="token annotation punctuation">@Bean</span>
    <span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"spring.datasource"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> DataSource <span class="token function">hikariDataSource</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Value</span><span class="token punctuation">(</span><span class="token string">"$&amp;#123;spring.datasource.url&amp;#125;"</span><span class="token punctuation">)</span>String url<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        HikariDataSource hikariDataSource <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HikariDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        hikariDataSource<span class="token punctuation">.</span><span class="token function">setJdbcUrl</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> hikariDataSource<span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>

    <span class="token comment" spellcheck="true">/**
     * 需要将 DataSourceProxy 设置为主数据源，否则事务无法回滚
     *
     * @param hikariDataSource The HikariDataSource
     * @return The default datasource
     */</span>
    <span class="token annotation punctuation">@Primary</span>
    <span class="token annotation punctuation">@Bean</span><span class="token punctuation">(</span><span class="token string">"dataSource"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> DataSource <span class="token function">dataSource</span><span class="token punctuation">(</span>DataSource hikariDataSource<span class="token punctuation">)</span> <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">DataSourceProxy</span><span class="token punctuation">(</span>hikariDataSource<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">1234567891011121314151617181920212223</span></code></pre>
<p>SpuInfoServiceImpl实现类保存商品信息的主方法：</p>
<pre class=" language-java"><code class="language-java">    <span class="token comment" spellcheck="true">/**
     * 保存商品信息
     * @param spuInfoVO
     */</span>
    <span class="token annotation punctuation">@GlobalTransactional</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">saveSpuInfoVO</span><span class="token punctuation">(</span>SpuInfoVO spuInfoVO<span class="token punctuation">)</span> <span class="token keyword">throws</span> FileNotFoundException <span class="token operator">&amp;</span>#<span class="token number">123</span><span class="token punctuation">;</span>
        <span class="token comment" spellcheck="true">/// 1.保存spu相关</span>
        <span class="token comment" spellcheck="true">// 1.1. 保存spu基本信息 spu_info</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">saveSpuInfo</span><span class="token punctuation">(</span>spuInfoVO<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// 1.2. 保存spu的描述信息 spu_info_desc</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>spuInfoDescService<span class="token punctuation">.</span><span class="token function">saveSpuDesc</span><span class="token punctuation">(</span>spuInfoVO<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// 1.3. 保存spu的规格参数信息</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">saveBaseAttrs</span><span class="token punctuation">(</span>spuInfoVO<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">/// 2. 保存sku相关信息</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">saveSkuInfoWithSaleInfo</span><span class="token punctuation">(</span>spuInfoVO<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// 最后制造异常</span>
        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token operator">&amp;</span>#<span class="token number">125</span><span class="token punctuation">;</span>
<span class="token number">1234567891011121314151617181920212223</span></code></pre>
<p>其他所有方法包括SpuInfoDescServiceImpl中的方法都只加普通注解：@Transactional</p>
<h3 id="3-4-3-改造gmall-sms"><a href="#3-4-3-改造gmall-sms" class="headerlink" title="3.4.3. 改造gmall-sms"></a>3.4.3. 改造gmall-sms</h3><p>pom.xml的依赖、file.conf、registry.conf、DataSourceConfig等等直接从gmall-pms拷贝过来即可</p>
<p>只需要把registry.conf中的如下内容修改一下：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702094018541.png" alt="在这里插入图片描述"></p>
<p>然后在业务方法上添加@Transactional注解即可。</p>
<h3 id="3-4-4-seata-server"><a href="#3-4-4-seata-server" class="headerlink" title="3.4.4. seata-server"></a>3.4.4. seata-server</h3><p>一旦我们使用注册中心，进行服务的发现，seata服务器也得配置放在注册中心。</p>
<p>修改registry.conf配置文件，把seata-server事务协调管理器也注册到nacos注册中心<br><img src="https://img-blog.csdnimg.cn/20200702094027799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>如下内容修改一下：</p>
<p><img src="https://img-blog.csdnimg.cn/20200702094036328.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>回到bin目录下，重新启动seata-server。</p>
<h3 id="3-4-5-主键问题"><a href="#3-4-5-主键问题" class="headerlink" title="3.4.5. 主键问题"></a>3.4.5. 主键问题</h3><p><img src="https://img-blog.csdnimg.cn/20200702094044638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p><img src="https://img-blog.csdnimg.cn/20200702094054380.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2EyNTIyODI3OTMx,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h3 id="-3"><a href="#-3" class="headerlink" title=""></a></h3><script>
        document.querySelectorAll('.github-emoji')
          .forEach(el => {
            if (!el.dataset.src) { return; }
            const img = document.createElement('img');
            img.style = 'display:none !important;';
            img.src = el.dataset.src;
            img.addEventListener('error', () => {
              img.remove();
              el.style.color = 'inherit';
              el.style.backgroundImage = 'none';
              el.style.background = 'none';
            });
            img.addEventListener('load', () => {
              img.remove();
            });
            document.body.appendChild(img);
          });
      </script>
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/about" rel="external nofollow noreferrer">guoxiaolong</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="http://carlosgxl.gitee.io/blog/2020/08/10/03-ben-di-shi-wu-fen-bu-shi-shi-wu/">http://carlosgxl.gitee.io/blog/2020/08/10/03-ben-di-shi-wu-fen-bu-shi-shi-wu/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/about" target="_blank">guoxiaolong</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/2020/08/10/08-dan-dian-deng-lu-yu-she-jiao-deng-lu/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/0.jpg" class="responsive-img" alt="">
                        
                        <span class="card-title"></span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
        document.querySelectorAll('.github-emoji')
          .forEach(el => {
            if (!el.dataset.src) { return
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2020-08-10
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-user fa-fw"></i>
                            guoxiaolong
                            
                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/2020/08/10/02-jie-kou-mi-deng-ci/">
                    <div class="card-image">
                        
                        
                        <img src="/medias/featureimages/0.jpg" class="responsive-img" alt="">
                        
                        <span class="card-title"></span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
        document.querySelectorAll('.github-emoji')
          .forEach(el => {
            if (!el.dataset.src) { return
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2020-08-10
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-user fa-fw"></i>
                            guoxiaolong
                            
                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/libs/codeBlock/codeShrink.js"></script>


<!-- 代码块折行 -->

<style type="text/css">
code[class*="language-"], pre[class*="language-"] { white-space: pre !important; }
</style>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h2, h3, h4').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="playlist"
                   id="503838841"
                   fixed='true'
                   autoplay='true'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="/libs/aplayer/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>

    
    <div class="container row center-align" style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            <span id="year">2019</span>
            <a href="/about" target="_blank">guoxiaolong</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            
            
            
            
            
            <span id="busuanzi_container_site_pv">
                |&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;<span id="busuanzi_value_site_pv"
                    class="white-color"></span>&nbsp;次
            </span>
            
            
            <span id="busuanzi_container_site_uv">
                |&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;<span id="busuanzi_value_site_uv"
                    class="white-color"></span>&nbsp;人
            </span>
            
            <br>
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://github.com/Guo119741" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:1197814431@qq.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=1197814431" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 1197814431" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>







    <a href="/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script src="/js/search.js"></script>
<script type="text/javascript">
$(function () {
    searchFunc("/search.xml", 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/libs/materialize/materialize.min.js"></script>
    <script src="/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/libs/aos/aos.js"></script>
    <script src="/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    

    

    

    
    <script src="/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
